using System;
using System.Collections.Generic;
using NUnit.Framework;

public class TestMonad<M> where M : MonadImpl<M>, new() {
    [Test]
    public void leftUnit() {
        var a = new Object();
        Assert.AreEqual(a, Monad.Star(Monad.Unit<Object, M>(() => a), b => Monad.Unit<Object, M>(() => b)).Just());
    }

    [Test]
    public void rightUnit() {
        var a = new Object();
        var m = Monad.Unit<Object, M>(() => a);
        Assert.AreEqual(
            Monad.Just(m),
            Monad.Just(
                Monad.Star(m, b => Monad.Unit<Object, M>(() => b))));
    }

    public Monad<String, M> an(Object o) {
        return Monad.Unit<String, M>(() => o.ToString());
    }

    public Monad<Int32, M> bo(Object o) {
        return Monad.Unit<Int32, M>(() => o.GetHashCode());
    }

    [Test]
    public void associative() {
        var m = Monad.Unit<Object, M>(() => new Object());
        Assert.AreEqual(
            Monad.Just(
                Monad.Star(
                    m,
                    a => Monad.Star(an(a), b => bo((String)b)))),
            Monad.Just(
                Monad.Star(
                    Monad.Star(m, a => an(a)),
                    b => bo(b))));
    }
}
[TestFixture]
public class TestIdentityMonad : TestMonad<IdentityMonadImpl> {
}
[TestFixture]
public class TestExceptionMonad : TestMonad<ExceptionMonadImpl> {
}
[TestFixture]
public class TestMaybeMonad : TestMonad<MaybeMonadImpl> {
    [Test]
    public void MaybeNot() {
        var mnil = Monad.Unit<String, MaybeMonadImpl>((String)null);
        Assert.IsNull(Monad.Just(mnil.Star(s => Monad.Unit<String, MaybeMonadImpl>(s.Substring(1)))));
    }

    private Monad<String, MaybeMonadImpl> lookup(string name, Dictionary<String, Monad<String, MaybeMonadImpl>> db) {
        Monad<String, MaybeMonadImpl> result;
        db.TryGetValue(name, out result);
        return result;
    }
        
    [Test]
    public void MailSystem() {
        //based very loosely on haskell.org's example
        // (I don't understand why their mplus is relevant)
        var fullnamedb = new Dictionary<String, Monad<String, MaybeMonadImpl>>();
        var nicknamedb = new Dictionary<String, Monad<String, MaybeMonadImpl>>();
        fullnamedb.Add("nicholas", Monad.Unit<String, MaybeMonadImpl>("n@m.com"));
        nicknamedb.Add("nick", Monad.Unit<String, MaybeMonadImpl>("nicholas"));
        var mnick = Monad.Unit<String, MaybeMonadImpl>("nick");
        Assert.AreEqual(
            "n@m.com",
            Monad.Star(
                Monad.Star(mnick, nick => lookup(nick, nicknamedb)),
                name => lookup(name, fullnamedb)).Just());
    }

    private class D {}
    private class C {
        public D d;
    }
    private class B {
        public C c;
    }
    private class A {
        public B b;
    }
    [Test]
    public void andand() {
        A nil = null;
        A a = new A();
        A ab = new A() { b = new B() };
        Assert.IsNull(MaybeMonad<D>.Andand<A>(() => null));
        Assert.IsNull(MaybeMonad<D>.Andand(() => (A)null));
        //Assert.IsNull(MaybeMonad<D>.Andand<A>(() => nil));
        Assert.AreEqual(a, MaybeMonad<D>.Andand(() => a));
        Assert.IsNull(MaybeMonad<D>.Andand(() => a.b));
        Assert.AreEqual(ab.b, MaybeMonad<D>.Andand(() => ab.b));
        Assert.IsNull(MaybeMonad<D>.Andand(() => ab.b.c));
        Func<Object> x = null;
        //        Assert.IsNull(MaybeMonad<D>.Andand<Object>(() => x()));
        Func<Func<Object>> y = null;
        //Assert.IsNull(MaybeMonad<D>.Andand(() => y()()));
        y = () => null;
        Assert.IsNull(MaybeMonad<D>.Andand(() => y()()));
        Assert.IsNull(MaybeMonad<D>.Andand(() => a.b.c));
        Assert.IsNull(MaybeMonad<D>.Andand(() => a.b.c.d));
    }
}
